package com.zhihao.domain.strategy.service.armory.algorithm.impl;

import com.zhihao.domain.strategy.model.entity.StrategyAwardEntity;
import com.zhihao.domain.strategy.service.armory.algorithm.AbstractAlgorithm;
import com.zhihao.domain.strategy.service.armory.algorithm.IAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.util.*;

/**
 * @author Jackson
 * @create 2025/4/7 9:50
 * @description O(1)-空间换时间 抽奖算法
 *              o(1) 时间复杂度算法，将抽奖概率分配到 Redis Map 结构中，1个概率对应的奖品就是一个Key，抽奖时直接通过 get 获取。
 */
@Slf4j
@Component("o1Algorithm")
public class O1Algorithm extends AbstractAlgorithm {

    /*
        实际应要求 概率总和严格等于1(不对的)
        代码通过 rateRange.multiply(awardRate).intValue() 计算每个奖品在查找表中的占位数量，但未对概率总和做校验。
            若概率总和 < 1：查找表会有空位（未填满），导致部分随机数无对应奖品（逻辑漏洞）。
            若概率总和 > 1：查找表实际长度超过 rateRange，但代码未处理越界问题（例如 rateRange=100，但实际生成 101 个元素）。

        ArrayList 的容量和逻辑大小是分开的。初始化后 size()=0，只有显式 add() 才会增加 size()。
        shuffle() 严格依赖 list.size()，与底层数组长度无关

        由于后期代码中使用的都是实际元素个数，size()代表实际add后的元素个数；所以最终的map中都是有效的奖品position和奖品awardId
            但是如果总和不等于1，那么奖品的中奖概率并不会是完全等于表中配置的award_rate
     */
    @Override
    public void armoryAlgorithm(String key, List<StrategyAwardEntity> strategyAwardEntities, BigDecimal rateRange) {
        log.info("抽奖算法 O(1) 装配 key:{}", key);
        // 1. 生成策略奖品概率查找表「这里指需要在list集合中，存放上对应的奖品占位即可，占位越多等于概率越高」
        List<Integer> strategyAwardSearchRateTables = new ArrayList<>(rateRange.intValue());    // 初始容量=1000，但 size()=0
        for (StrategyAwardEntity strategyAward : strategyAwardEntities) {
            Integer awardId = strategyAward.getAwardId();
            BigDecimal awardRate = strategyAward.getAwardRate();
            // 计算出每个概率值需要存放到查找表的数量，循环填充
            for (int i = 0; i < rateRange.multiply(awardRate).intValue(); i++) {
                strategyAwardSearchRateTables.add(awardId);     // size()=概率总和*rateRange
            }
        }

        // 2. 对存储的奖品进行乱序操作(所有奖品的个数比例是没变化的，这一步目的是为了加强随机性)
        Collections.shuffle(strategyAwardSearchRateTables);

        // 3. 生成出Map集合，key值，对应的就是后续的概率值。通过概率来获得对应的奖品ID
        Map<Integer, Integer> shuffleStrategyAwardSearchRateTable = new LinkedHashMap<>();
        for (int i = 0; i < strategyAwardSearchRateTables.size(); i++) {
            shuffleStrategyAwardSearchRateTable.put(i, strategyAwardSearchRateTables.get(i));
        }
        // 实际表中最终存储的奖品数量 as 后续抽奖的随机数生成范围 保证了即使各奖品概率和不为1，也有正确的比例
        int raffleRandomNumRange = shuffleStrategyAwardSearchRateTable.size();

        // 4. 存放到 Redis
        repository.storeStrategyAwardSearchRateTable(key, raffleRandomNumRange, shuffleStrategyAwardSearchRateTable);
    }

    @Override
    public Integer dispatchAlgorithm(String key) {
        log.info("抽奖算法 O(1) 抽奖计算 key:{}", key);
        // 分布式部署下，不一定为当前应用做的策略装配。也就是值不一定会保存到本应用，而是分布式应用，所以需要从 Redis 中获取。
        int rateRange = repository.getRateRange(key);
        // 通过生成的随机值，获取概率值奖品查找表的结果
        return repository.getStrategyAwardAssemble(key, secureRandom.nextInt(rateRange));
    }

}
